#include "filters.h"
#include "pixelMap.h"
#include <GL/glui.h>
#include <iostream>


///Acoes GLUI
#define FILTER          0x01
#define FOURIER_DIRECT  0x02
#define FOURIER_INVERSE 0x03
#define LOAD_IMAGE      0x04

#define COPY_COMPLEX(dst, src)             \
{                                          \
    (dst).real = (src).real;               \
    (dst).imag = (src).imag;               \
    (dst).module = (src).module;           \
}

using namespace std;

void idleFunc( void );
void pictureDisplay( void );
void pictureInit( void );
void keyboard( unsigned char key, int x, int y );
void initMatrixes();
void reloadImage();
void freeMatrixes();
void viewSpectre(COMPLEX** matrix, bool forward, bool centralised);
void setComplex();
void centralising( COMPLEX** matrix, int width, int height );

///Funcoes GLUI
void controlCallback (int control);

void setPictures(char* img);

int winMain;

PixelMap* pixelMap;
PixelMap* pixelMapResult;
PixelMap* imageMap;
pixel** pixels;
pixel** resultPixels;
COMPLEX** matrixIn;
COMPLEX** matrixOut;
uByte *data;
int LBLP_Size = 0;

///Variaveis do GLUI
char image[30] = "cube.bmp";

GLUI* glui;
GLUI_Panel* panelMain;

GLUI_Panel* panelFilter;
GLUI_Button* buttonDFourier;
GLUI_Button* buttonIFourier;

GLUI_Panel* panelImage;
GLUI_EditText *editImage;
GLUI_Button* buttonImage;

GLUI_Panel* panelRadius;
GLUI_EditText *editRadius;

int main(int argc, char** argv)
{
    setPictures(image);
    initMatrixes();
    glutInit( &argc, argv );

    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
    glutInitWindowSize( pixelMap->GetWidth(), pixelMap->GetHeight() );
    glutInitWindowPosition( 100, 100 );
    winMain = glutCreateWindow ( "Resultado" );
    glutKeyboardFunc( keyboard );
    glutDisplayFunc( pictureDisplay );
    pictureInit();

    glui = GLUI_Master.create_glui( "Filter Control", 0, 1000, 100 );
    panelMain = glui->add_panel( "", GLUI_PANEL_NONE );

    panelFilter = glui->add_panel_to_panel( panelMain, "Fourier" );
    buttonDFourier = glui->add_button_to_panel( panelFilter, "Apply Fourier", FOURIER_DIRECT, controlCallback);
    buttonDFourier = glui->add_button_to_panel( panelFilter, "Apply Inverse", FOURIER_INVERSE, controlCallback);

    panelImage = glui->add_panel_to_panel( panelMain, "Image" );
    editImage = glui->add_edittext_to_panel( panelImage, "Image: " );
    buttonImage = glui->add_button_to_panel( panelImage, "Set", LOAD_IMAGE, controlCallback );

    panelRadius = glui->add_panel_to_panel( panelMain, "Filter" );
    editRadius = glui->add_edittext_to_panel( panelRadius, "Radius: ", GLUI_EDITTEXT_FLOAT );

    glui->set_main_gfx_window( winMain );
    GLUI_Master.set_glutIdleFunc( idleFunc );

    glutMainLoop();
    return 0;
}

void idleFunc( void )
{
    if ( glutGetWindow() !=  winMain) glutSetWindow(winMain);
    glutPostRedisplay();
}

void keyboard( unsigned char key, int x, int y )
{
    switch( key )
    {
        case 'H':
        case 'h':
            cout << "Applying High Pass..." << endl;
            highPass( matrixOut, pixelMap->GetHeight(), pixelMap->GetWidth(), (double)editRadius->get_float_val() );
            viewSpectre( matrixOut, true, false );
            cout << "Done!" << endl;
            break;
        case 'L':
        case 'l':
            cout << "Applying Low Pass..." << endl;
            lowPass( matrixOut, pixelMap->GetHeight(), pixelMap->GetWidth(), (double)editRadius->get_float_val() );
            viewSpectre( matrixOut, true, false );
            cout << "Done!" << endl;
            break;
        case 'F':
        case 'f':
            cout << "Applying Direct Fourier..." << endl;
            controlCallback( FOURIER_DIRECT );
            cout << "Done!" << endl;
            break;
        case 'I':
        case 'i':
            cout << "Applying Inverse Fourier..." << endl;
            controlCallback( FOURIER_INVERSE );
            cout << "Done!" << endl;
            break;
        default: break;
    }
}

void pictureDisplay( void )
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    pixelMap->ViewImage();
    glutSwapBuffers();
    glutPostRedisplay();
}


void pictureInit ( void )
{
    // selecionar cor de fundo (preto)
    glClearColor( 0.0, 0.0, 0.0, 0.0 );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix
    glLoadIdentity();							// Inicializa com matriz identidade
}

void setPictures( char* img)
{
    pixelMap = new PixelMap(img);
    pixelMap->ConvertToGrayScale();
    imageMap = pixelMap;
}


void controlCallback( int control )
{
    switch ( control )
    {
    case LOAD_IMAGE:
    {
        reloadImage();
        break;
    }
    case FOURIER_DIRECT:
    {
        setComplex();
        if( !FFT2D( matrixIn, matrixOut, pixelMap->GetHeight(), pixelMap->GetWidth(), 1 ) ) cout << "Erro!" << endl;
        //DFT(1, pixelMap->GetHeight(), pixelMap->GetWidth(), matrixIn, matrixOut);
        viewSpectre( matrixOut, true, true );
        break;
    }
    case FOURIER_INVERSE:
    {
        if( !FFT2D( matrixOut, matrixIn, pixelMap->GetHeight(), pixelMap->GetWidth(), -1 ) ) cout << "Erro!" << endl;
        //DFT(-1, pixelMap->GetHeight(), pixelMap->GetWidth(), matrixOut, matrixIn);
        viewSpectre( matrixIn, false, false );
        break;
    }
    default:
        break;
    }
}

void initMatrixes()
{
    pixels = (pixel **) malloc(pixelMap->GetHeight() * sizeof(pixel *));
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        pixels[i] = (pixel *) malloc(pixelMap->GetWidth() * sizeof(pixel));
    }
    resultPixels = (pixel **) malloc(pixelMap->GetHeight() * sizeof(pixel *));
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        resultPixels[i] = (pixel *) malloc(pixelMap->GetWidth() * sizeof(pixel));
    }

    matrixIn = (COMPLEX **) malloc(pixelMap->GetHeight() * sizeof(COMPLEX *));
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
            matrixIn[i] = (COMPLEX *) malloc(pixelMap->GetWidth() * sizeof(COMPLEX));;
    }

    matrixOut = (COMPLEX ** ) malloc(pixelMap->GetHeight() * sizeof(COMPLEX *));
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
            matrixOut[i] = (COMPLEX *) malloc(pixelMap->GetWidth() * sizeof(COMPLEX));;
    }


    pixelMap->ConvertDataToPixels(pixels);

}

void freeMatrixes()
{
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        free(pixels[i]);
        free(resultPixels[i]);
        free(matrixIn[i]);
        free(matrixOut[i]);
    }
    free(pixels);
    free(resultPixels);
    free(matrixIn);
    free(matrixOut);
}

void setComplex()
{
    for (int i = 0; i < pixelMap->GetHeight(); i++)
    {
        for (int j = 0; j < pixelMap->GetWidth(); j++)
        {
            matrixIn[i][j].real = (double)pixels[i][j].value;
            matrixIn[i][j].imag = 0.0;
        }
    }
}

void viewSpectre(COMPLEX** matrix, bool forward, bool centralised)
{
    double c;
    if( forward ) c = 255.0/( log10( 1 + getGreaterModule() ) );
    if( centralised ) centralising(matrix, pixelMap->GetWidth(), pixelMap->GetHeight());

    for( int i = 0; i < pixelMap->GetHeight(); i++ )
    {
        for( int j = 0; j < pixelMap->GetWidth(); j++ )
        {
            double value = ( forward ) ? ( c*log10( 1 + matrix[i][j].module ) ) : matrix[i][j].module;
            ///Applying boundary condition of the color
            resultPixels[i][j].value = ( value > 255.0 ) ? 255.0 : ( ( value < 0.0 ) ? 0.0 : value );
        }
    }
    pixelMap->ConvertPixelsToData(resultPixels);
    pixelMap->ViewImage();
}

void reloadImage()
{
    freeMatrixes();
    pixelMap->Read(editImage->get_text());
    pixelMap->ConvertToGrayScale();
    initMatrixes();
    glutReshapeWindow(pixelMap->GetWidth(), pixelMap->GetHeight());
}

void centralising( COMPLEX** matrix, int width, int height )
{
    int height_2 = height/2;
    int width_2 = width/2;
    int delta = width_2;

    for( int i = 0; i < height_2; i++ )
    {
        for( int j = 0; j < width_2; j++ )
        {
            COMPLEX aux;
            COPY_COMPLEX(aux, matrix[i][j]);
            COPY_COMPLEX(matrix[i][j], matrix[i + delta][j + delta]);
            COPY_COMPLEX(matrix[i + delta][j + delta], aux);
        }
        for( int j = width_2; j < width; j++ )
        {
            COMPLEX aux;
            COPY_COMPLEX(aux, matrix[i][j]);
            COPY_COMPLEX(matrix[i][j], matrix[i + delta][j - delta]);
            COPY_COMPLEX(matrix[i + delta][j - delta], aux);
        }
    }
}
